package datatypes;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import datatypes.Datatypes.tableType;

/**
 * defines all Freebase data types to be used by query requests, as well as all
 * the connections between them
 * 
 * @author Micha
 * 
 */

public class Datatypes {

	/* --- flags --- */
	
	private static boolean useViewsOptimization = false;
	
	public static enum connectionType {

		/*
		 * -------------------------------------------------------------------
		 * -------------------- connection types -----------------------------
		 * -------------------------------------------------------------------
		 */

		Regular_Appearances("TV_Play"),

		Created("Created"),

		Film_Play("Film_Play"),

		Directs("Directs");

		private String connectionTableName;
		private String contextIdName;
		private String connectorIdName;

		connectionType(String connectionTableName) {
			setConnectionTableName(connectionTableName);
			setConnectorIdName(null);
			setContextIdName(null);
		}

		public String getConnectionTableName() {
			return connectionTableName;
		}

		public void setConnectionTableName(String connectionTableName) {
			this.connectionTableName = connectionTableName;
		}

		public String getContextIdName() {
			return contextIdName;
		}

		public void setContextIdName(String contextIdName) {
			this.contextIdName = contextIdName;
		}

		public String getConnectorIdName() {
			return connectorIdName;
		}

		public void setConnectorIdName(String connectorIdName) {
			this.connectorIdName = connectorIdName;
		}

	}

	/**
	 * TODO: update here all table real names in database
	 * 
	 * tableType represents all table types in the database, each table type has
	 * a tableRep representing the table actual name in the database, and a list
	 * of table connections
	 * 
	 */
	public static enum tableType {

		/*
		 * -------------------------------------------------------------------
		 * ----------------------------- tv types ----------------------------
		 * -------------------------------------------------------------------
		 */

		tvProgram("TV_Program", "KnownProgramView", "Id", "Name"),

		tvActor("TV_Actor", "", "Id", "Name", tvProgram),

		tvGenre("Genre", "", "Id", "Name"),

		tvProgramCreator("Program_Creator", "KnownCreatorsView", "Id", "Name",
				tvProgram),

		tvGuestRole("guest_roles", "", "id", "name"),

		/*
		 * -------------------------------------------------------------------
		 * ----------------------------- film types ----------------------------
		 * -------------------------------------------------------------------
		 */

		film("Film", "KnownFilmView", "Id", "Name"),

		filmDirector("Director", "KnownDirectorsView", "Id", "Name", film),

		filmActor("Film_Actor", "", "Id", "Name", film),

		filmRole("film_roles", "", "id", "name"),
		
		tvStarringRole("", "", "", "");

		/**
		 * represents the actual name of the table in the database
		 */
		private String tableName;

		/**
		 * represents the actual name of the all known types table in the
		 * database
		 */
		private String knownTableName;

		/**
		 * represents the type identification
		 */
		private String typeId;

		/**
		 * represents the type name
		 */
		private String typeName;

		/**
		 * represents a list of connections for each type. for example tvActor
		 * is connected to tvProgram because TV actor plays/played in TV
		 * program.
		 * 
		 * connections are symmetric, that means that if a tvActor is connected
		 * (appears in the list of connections) to tvProgram then tvProgram is
		 * automatically added to the connection list of tvActor.
		 * 
		 * Formally: -> represents the connection direction T1 -> T2 ===> T2 ->
		 * T1
		 * 
		 */
		private List<tableType> connections;

		/*
		 * -------------------------------------------------------------------
		 * --------------------------- constructors --------------------------
		 * -------------------------------------------------------------------
		 */

		private Random randomizer;

		tableType() {
			tableName = null;
			connections = new ArrayList<tableType>();
			randomizer = new Random();

		}

		tableType(String tableRep, String knownTableName, String typeId,
				String typeName, tableType... connections) {
			this.tableName = tableRep;

			if (knownTableName.length() > 0 && useViewsOptimization) {
				this.setKnownTableName(knownTableName);
			} else {
				this.setKnownTableName(this.tableName);
			}

			this.setTypeId(typeId);
			this.setTypeName(typeName);
			this.connections = new ArrayList<tableType>();
			randomizer = new Random();
			if (connections == null)
				return;

			// add all connections to connection list
			for (tableType connection : connections) {

				// add current connection to the connection list
				this.connections.add(connection);

				// add current data type to current connection list
				connection.addConnection(this);
			}
		}

		/*
		 * -------------------------------------------------------------------
		 * ------------------------- member functions ------------------------
		 * -------------------------------------------------------------------
		 */

		/**
		 * adds a new connection to the connection list
		 */
		public void addConnection(tableType connection) {
			connections.add(connection);
		}

		/**
		 * @return true iff types are connected
		 */
		public boolean isConnectedTo(tableType connection) {
			return this.connections.contains(connection)
					|| connection.connections.contains(this);
		}

		/**
		 * @return a random connection of the type
		 */
		public tableType getRandomConnection() {

			int numOfConnections = this.connections.size();

			if (numOfConnections == 0)
				return null;

			int randIndex = randomizer.nextInt(numOfConnections);

			return this.connections.get(randIndex);
		}

		/**
		 * @return an allowed connection of the type (all the connections which
		 *         does not appear in the forbidden connections array)
		 */
		public tableType getRandomConnectionWithException(
				tableType... forbiddenConnections) {

			List<tableType> allowedConnections = new ArrayList<Datatypes.tableType>();
			boolean forbidden = false;
			for (tableType conn : this.connections) {

				for (tableType forbConn : forbiddenConnections) {
					if (conn.equals(forbConn)) {
						forbidden = true;
						break;
					}
				}

				if (forbidden == false) {
					allowedConnections.add(conn);
				}

				forbidden = false;

			}

			int numOfConnections = allowedConnections.size();

			if (numOfConnections == 0)
				return null;

			int randIndex = randomizer.nextInt(numOfConnections);
			return allowedConnections.get(randIndex);
		}

		@Override
		public String toString() {
			return name();
		}

		public String getTableName() {

			return this.tableName;
		}

		public String getTypeRep() {

			switch (this) {

			case tvProgram: {
				return "TV program";
			}

			case tvActor: {
				return "actor";
			}

			case tvGenre: {
				return "genre";
			}

			case tvProgramCreator: {
				return "creator";
			}

			case film: {
				return "movie";
			}

			case filmDirector: {
				return "director";
			}

			case filmActor: {
				return "actor";
			}

			}

			return "";
		}

		/**
		 * @return the connection representation between any two connection
		 * 
		 *         'this' is the context in our case (in which the @param
		 *         contextTitle is referencing too), and the connection is the
		 *         asked context (which will be represented by a blank)
		 * 
		 */
		public String getConnectionRep(tableType connector, String contextInfo) {

			// check if types are actually connected
			if (!this.isConnectedTo(connector))
				return null;

			StringBuilder sb = new StringBuilder();
			switch (this) {

			case tvProgram: {

				if (!this.isConnectedTo(connector))
					return null;

				switch (connector) {
				case tvActor: {
					return "the " + tvActor.getTypeRep() + blank
							+ " played in the " + tvProgram.getTypeRep()
							+ " \"" + contextInfo + "\"";
				}

				case tvProgramCreator: {
					return "the " + tvProgramCreator.getTypeRep() + blank
							+ " created the " + tvProgram.getTypeRep() + " \""
							+ contextInfo + "\"";
				}

				default:
					break;

				}
			}

			case tvActor: {

				switch (connector) {
				case tvProgram: {
					return "the " + tvActor.getTypeRep() + " \"" + contextInfo
							+ "\"" + " played in the " + tvProgram.getTypeRep()
							+ blank;
				}
				default:
					break;

				}

			}

			case tvGenre: {
				break;
			}

			case tvProgramCreator: {
				switch (connector) {
				case tvProgram: {
					return "the " + tvProgramCreator.getTypeRep() + " \""
							+ contextInfo + "\"" + " created the "
							+ tvProgram.getTypeRep() + blank;
				}
				default:
					break;

				}
			}

			case tvGuestRole: {
				break;
			}

			case film: {

				sb.append("the " + film.getTypeRep() + " \"" + contextInfo
						+ "\"");

				switch (connector) {

				case filmDirector: {
					return sb.toString() + " was directed by" + blank;
				}

				case filmActor: {
					return "the " + filmActor.getTypeRep() + " " + blank
							+ " played in " + sb.toString();
				}
				default:
					break;

				}

				break;
			}

			case filmDirector: {

				sb.append("the " + filmDirector.getTypeRep() + " \""
						+ contextInfo + "\"");

				switch (connector) {
				case film: {
					return sb.toString() + " directed the " + film.getTypeRep()
							+ blank;
				}
				default:
					break;

				}

			}

			case filmActor: {

				sb.append("the " + filmActor.getTypeRep() + " \"" + contextInfo
						+ "\"");

				switch (connector) {
				case film: {
					return sb.toString() + " played in the "
							+ film.getTypeRep() + blank;
				}
				default:
					break;

				}

			}

			case filmRole: {
				break;
			}

			}

			// Ideally shuld'nt get here
			if (sb.toString().equals("")) {
				return "connection rep undefined";
			}

			return sb.toString();
		}

		public String getTypeId() {
			return typeId;
		}

		public void setTypeId(String typeId) {
			this.typeId = typeId;
		}

		public String getTypeName() {
			return typeName;
		}

		public void setTypeName(String typeName) {
			this.typeName = typeName;
		}

		public String getKnownTableName() {
			return knownTableName;
		}

		public void setKnownTableName(String knownTableName) {
			this.knownTableName = knownTableName;
		}

		/**
		 * @return random connection of the type if the elimination type is the
		 *         only connection to the type, return it in any other case,
		 *         return one of the other types
		 */
		public tableType getRandomConnection(tableType eliminiationType) {

			List<tableType> connsWithoutElim = new ArrayList<tableType>();

			for (tableType conn : this.connections) {
				if (!conn.equals(eliminiationType)) {
					connsWithoutElim.add(conn);
				}
			}

			if (connsWithoutElim.size() == 0
					&& this.isConnectedTo(eliminiationType)) {
				return eliminiationType;
			}

			return getRandomConnectionWithException(eliminiationType);
		}
	}

	private static String blank = " _____";

	/**
	 * @return random type
	 */
	public static tableType getRandomTypeFrom(tableType... types) {
		Random randomizer = new Random();

		return types[randomizer.nextInt(types.length)];
	}

	public static connectionType getConnectionTableName(tableType context,
			tableType connector) {

		// tv program -> tv actor connection
		if (context.equals(tableType.tvProgram)
				&& connector.equals(tableType.tvActor)) {
			connectionType.Regular_Appearances.setContextIdName("Program_Id");
			connectionType.Regular_Appearances.setConnectorIdName("Actor_Id");
			return connectionType.Regular_Appearances;
		}

		// tv program <- tv actor connection
		if (connector.equals(tableType.tvProgram)
				&& context.equals(tableType.tvActor)) {
			connectionType.Regular_Appearances.setContextIdName("Actor_Id");
			connectionType.Regular_Appearances.setConnectorIdName("Program_Id");
			return connectionType.Regular_Appearances;
		}

		// tv program -> program creator connection
		if (context.equals(tableType.tvProgram)
				&& connector.equals(tableType.tvProgramCreator)) {
			connectionType.Created.setContextIdName("Program_Id");
			connectionType.Created.setConnectorIdName("Program_Creator_Id");
			return connectionType.Created;
		}

		// tv program <- program creator connection
		if (connector.equals(tableType.tvProgram)
				&& context.equals(tableType.tvProgramCreator)) {
			connectionType.Created.setContextIdName("Program_Creator_Id");
			connectionType.Created.setConnectorIdName("Program_Id");
			return connectionType.Created;
		}

		// film <- film actor connection
		if (context.equals(tableType.filmActor)
				&& connector.equals(tableType.film)) {
			connectionType.Film_Play.setContextIdName("Actor_Id");
			connectionType.Film_Play.setConnectorIdName("Film_Id");
			return connectionType.Film_Play;
		}

		// film -> film actor connection
		if (connector.equals(tableType.filmActor)
				&& context.equals(tableType.film)) {
			connectionType.Film_Play.setContextIdName("Film_Id");
			connectionType.Film_Play.setConnectorIdName("Actor_Id");
			return connectionType.Film_Play;
		}

		// film -> film director connection
		if (context.equals(tableType.film)
				&& connector.equals(tableType.filmDirector)) {
			connectionType.Directs.setContextIdName("Film_Id");
			connectionType.Directs.setConnectorIdName("Director_Id");
			return connectionType.Directs;
		}

		// film <- film director connection
		if (connector.equals(tableType.film)
				&& context.equals(tableType.filmDirector)) {
			connectionType.Directs.setContextIdName("Director_Id");
			connectionType.Directs.setConnectorIdName("Film_Id");
			return connectionType.Directs;
		}

		return null;

	}

}
